package p2pmodule;

import java.util.ArrayList;
import java.util.List;

import com.kenmccrary.jtella.ConnectionData;
import com.kenmccrary.jtella.GNUTellaConnection;
import com.kenmccrary.jtella.HostCache;
import com.kenmccrary.jtella.NodeConnection;

import dustconf.DustConf;
import dustdispatcher.DustDispatcher;
import events.FileDownloadedListener;
import events.FileFoundListener;

/**
 * Class that implements the concept of node in a Gnutella network.
 * 
 * @author ole
 */
public class GnutellaServant {
	// Class constants.
	private final int DEFAULT_SERVANT_PORT = 6346;
	private final String DUST_AGENT_HEADER = "Dust";
	
	// Class attributes.
	private GNUTellaConnection connection = null;
	private List<Searcher> searchs = null;
	
	/*
	 * Public methods of class GnutellaServant.
	 */
	
	/**
	 * Class constructor. Initialize this servant and connect it to the
	 * Gnutella network.
	 * 
	 * NOTE: The port where this servant will listen is different from the port
	 * the FileServer will listen. Gnutella just helps to find where a file can
	 * be encountered, not to download it. In order to do it a different server
	 * implementing another protocol must be created. That functionality is
	 * implemented in the FileServer class.
	 * 
	 * @param gnutella_port:
	 * 		The port where the Gnutella servant will listen for messages and
	 * 		will send messages to the network. If 0 or less is passed then the
	 * 		default port will be taken (port 6346).
	 * @param server:
	 * 		The FileServer instance that will complement this GnutellaServant.
	 * 		Note that Gnutella is just a protocol for file search, not
	 *		transfer, so it needs to be complemented with a external file
	 *		server that is capable of transfer files. The GnutellaServant needs
	 *		it to inform in the QUERYHITs where to connect to download files.
	 * @throws Exception:
	 * 		If shared_dir doesn't references a directory.
	 */
	public GnutellaServant(int port, FileServer server)
	throws Exception {
		ConnectionData conn_data;
		HostCache peers_cache;
		String ip;
		
		this.searchs = new ArrayList<Searcher>();
		
		// Initialize the connection to the Gnutella network. This peer needs
		// to be ultrapeer because if it doesn't then it will close sessions
		// started by other peers.
		conn_data = new ConnectionData();
		conn_data.setIncomingPort(port <= 0? DEFAULT_SERVANT_PORT: port);
		conn_data.setAgentHeader(DUST_AGENT_HEADER);
		conn_data.setUltrapeer(true);
		connection = new GNUTellaConnection(conn_data);
		
		// Add to that connection the peers we know.
		peers_cache = connection.getHostCache();
		for (Peer peer: DustConf.getPeers()) {
			// Why removing the first character? InetAddress.toString() puts
			// as first character a '/', that makes addHost() method goes
			// crazy.
			ip = peer.getAddr().toString().substring(1);
			peers_cache.addHost(ip, peer.getPort());
		}
		
		// Sets the monitor for the QUERY messages.
		connection.getSearchMonitorSession(new QueryHandler(connection,
															server));
		connection.start();
	}
	
	/**
	 * Perform the download of the Item specified. This method is asynchronous,
	 * a new Thread will be created and it will do the download.
	 * 
	 * @param item:
	 * 		The Item to be downloaded.
	 * @param callbacks:
	 * 		A List of Listeners to be executed when the Item is already
	 * 		downloaded.
	 */
	public void download(Item item, List<FileDownloadedListener> callbacks) {
		ClientTransfer transfer;
		
		// Create the Transfer instance and add to it the callbacks in the
		// list if it exists.
		transfer = new ClientTransfer(item, DustConf.getTempFolder());
		if (callbacks != null)
			for (FileDownloadedListener callback: callbacks)
				transfer.addFileDownloadedListener(callback);
		
		//transfer.start();
		DustDispatcher.dispatchTask(transfer);
	}
	
	/**
	 * Perform a search in the Gnutella network. If something is found each
	 * callback is execute.
	 * 
	 * @param pattern:
	 * 		Pattern to search in the filenames.
	 * @param callbacks:
	 * 		A List of FileFoundListeners that will be executed when a QUERYHIT
	 * 		arrived.
	 */
	public void search(String pattern, List<FileFoundListener> callbacks) {
		Searcher searcher;
		
		// Create a new Searcher to handle the search request.
		searcher = new Searcher(pattern, connection);
		
		// Add it to the list of open search sessions.
		searchs.add(searcher);
		/** TODO: When the search is finished we must remove it! **/
		
		// Add to it the callbacks for when a file is found.
		if (callbacks != null)
			for (FileFoundListener callback: callbacks)
				searcher.addFileFoundListener(callback);
		
		// Do the search.
		searcher.performSearch();
	}
	
	/**
	 * Stops the connection to the Gnutella network. Once the servant has been
	 * disconnected it cannot be used again.
	 */
	public void shutdown() {
		// Close all open connections to other Gnutella servants.
		for (NodeConnection open_conn: connection.getConnectionList())
			open_conn.shutdown();
		
		// Close all SearchSessions.
		for (Searcher searcher: searchs)
			searcher.finish();
		
		// Close this Gnutella servant.
		connection.stop();
	}
}
